﻿using System;
using System.Data.Services;
using System.Data.Services.Common;
using System.Data.Services.Providers;
using System.IO;
using System.ServiceModel;
using System.Web;
using Ninject;
using NuGet.Enterprise.Server.Models;
using System.Web.Routing;
using NuGet.Enterprise.Server.Configuration;

namespace NuGet.Enterprise.Server.DataServices
{
    [ServiceBehavior(IncludeExceptionDetailInFaults = true)]
    public class Packages
        : DataService<PackagesSource>, IDataServiceStreamProvider, IServiceProvider
    {
        private static readonly NLog.Logger log = NLog.LogManager.GetCurrentClassLogger();

        /// <summary>
        /// Initializes the service.
        /// </summary>
        /// <param name="config">The config.</param>
        public static void InitializeService(DataServiceConfiguration config)
        {
            config.SetEntitySetAccessRule("Packages", EntitySetRights.AllRead);
            config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
            config.UseVerboseErrors = true;
        }

        /// <summary>
        /// Creates the data source.
        /// </summary>
        /// <returns></returns>
        protected override PackagesSource CreateDataSource()
        {
            var packageRepository = Application.Kernel.Get<IServerPackageRepository>();

            return new PackagesSource(packageRepository);
        }

        /// <summary>
        /// This method is invoked by the data services framework to obtain metadata about the stream associated with the specified <paramref name="entity"/>.
        /// </summary>
        /// <param name="entity">The entity for which the descriptor object should be returned.</param>
        /// <param name="operationContext">The <see cref="T:System.Data.Services.DataServiceOperationContext"/> instance  that processes the request.</param>
        /// <exception cref="T:System.ArgumentNullException">When <paramref name="entity"/> or <paramref name="operationContext"/> are null.</exception>
        ///   
        /// <exception cref="T:System.ArgumentException">When <paramref name="entity"/> is not an entity that has a binary property to stream.</exception>
        ///   
        /// <exception cref="T:System.Data.Services.DataServiceException">When the stream associated with the <paramref name="entity"/> cannot be deleted.</exception>
        public void DeleteStream(object entity, DataServiceOperationContext operationContext)
        {
            throw new NotSupportedException();
        }

        /// <summary>
        /// Returns the default stream that is associated with an entity that has a binary property.
        /// </summary>
        /// <param name="entity">The entity that has the associated binary stream.</param>
        /// <param name="etag">The eTag value sent as part of the HTTP request that is sent to the data service.</param>
        /// <param name="checkETagForEquality">A nullable <see cref="T:System.Boolean"/> value that determines the type of eTag that is used.</param>
        /// <param name="operationContext">The <see cref="T:System.Data.Services.DataServiceOperationContext"/> instance used by the data service to process the request.</param>
        /// <returns>
        /// The data <see cref="T:System.IO.Stream"/> that contains the binary property data of the <paramref name="entity"/>.
        /// </returns>
        public Stream GetReadStream(object entity, string etag, bool? checkETagForEquality, DataServiceOperationContext operationContext)
        {
            throw new NotSupportedException();
        }

        /// <summary>
        /// Returns the URI that is used to request the data stream that is associated with the binary property of an entity.
        /// </summary>
        /// <param name="entity">The entity that has the associated binary data stream.</param>
        /// <param name="operationContext">The <see cref="T:System.Data.Services.DataServiceOperationContext"/> instance used by the data service to process the request.</param>
        /// <returns>
        /// A <see cref="T:System.Uri"/> value that is used to request the binary data stream.
        /// </returns>
        public Uri GetReadStreamUri(object entity, DataServiceOperationContext operationContext)
        {
            var package = entity as PackagesEntity;

            if (package != null)
            {
                HttpContextBase contextBase = new HttpContextWrapper(HttpContext.Current);
                RouteData routeData = RouteTable.Routes.GetRouteData(contextBase);
                RequestContext requestContext = new RequestContext(contextBase, routeData);

                string connectionName = (requestContext.RouteData.Values["connectionName"] as string) ?? BuildStoreSection.Current.Connections.DefaultConnectionName;

                using (NLog.NestedDiagnosticsContext.Push("ConnectionContext: {0}".FormatInvariant(connectionName)))
                {
                    Uri downloadUrl = new Uri(operationContext.AbsoluteServiceUri, VirtualPathUtility.ToAbsolute("~/feed/{0}/Packages/Download/{1}.{2}".FormatInvariant(connectionName, package.Id, package.Version) + Constants.PackageExtension));

                    log.Debug("Calculated package download URL: {0}", downloadUrl);

                    return downloadUrl;
                }
            }

            throw new NotSupportedException();
        }

        /// <summary>
        /// Returns the content type of the stream that is associated with the specified entity.
        /// </summary>
        /// <param name="entity">The entity that has the associated binary data stream.</param>
        /// <param name="operationContext">The <see cref="T:System.Data.Services.DataServiceOperationContext"/> instance used by the data service to process the request.</param>
        /// <returns>
        /// A valid Content-Type of the binary data.
        /// </returns>
        public string GetStreamContentType(object entity, DataServiceOperationContext operationContext)
        {
            return "application/zip";
        }

        /// <summary>
        /// Returns the eTag of the data stream that is associated with the specified entity.
        /// </summary>
        /// <param name="entity">The entity that has the associated binary data stream.</param>
        /// <param name="operationContext">The <see cref="T:System.Data.Services.DataServiceOperationContext"/> instance used by the data service to process the request.</param>
        /// <returns>
        /// eTag of the stream associated with the <paramref name="entity"/>.
        /// </returns>
        public string GetStreamETag(object entity, DataServiceOperationContext operationContext)
        {
            return null;
        }

        /// <summary>
        /// Returns the stream that the data service uses to write the contents of a binary property that is associated with an entity.
        /// </summary>
        /// <param name="entity">The entity that has the associated binary stream.</param>
        /// <param name="etag">The eTag value that is sent as part of the HTTP request that is sent to the data service.</param>
        /// <param name="checkETagForEquality">A nullable <see cref="T:System.Boolean"/> value that determines the type of eTag is used.</param>
        /// <param name="operationContext">The <see cref="T:System.Data.Services.DataServiceOperationContext"/> instance that is used by the data service to process the request.</param>
        /// <returns>
        /// A valid <see cref="T:System.Stream"/> the data service uses to write the contents of a binary property that is associated with the <paramref name="entity"/>.
        /// </returns>
        public Stream GetWriteStream(object entity, string etag, bool? checkETagForEquality, DataServiceOperationContext operationContext)
        {
            throw new NotSupportedException();
        }

        /// <summary>
        /// Returns a namespace-qualified type name that represents the type that the data service runtime must create for the Media Link Entry that is associated with the data stream for the Media Resource that is being inserted.
        /// </summary>
        /// <param name="entitySetName">Fully-qualified entity set name.</param>
        /// <param name="operationContext">The <see cref="T:System.Data.Services.DataServiceOperationContext"/> instance that is used by the data service to process the request.</param>
        /// <returns>
        /// A namespace-qualified type name.
        /// </returns>
        public string ResolveType(string entitySetName, DataServiceOperationContext operationContext)
        {
            throw new NotSupportedException();
        }

        /// <summary>
        /// Gets the size of the stream buffer.
        /// </summary>
        /// <returns>Integer that represents the size of buffer.</returns>
        public int StreamBufferSize
        {
            get
            {
                return 64000;
            }
        }

        /// <summary>
        /// Gets the service object of the specified type.
        /// </summary>
        /// <param name="serviceType">An object that specifies the type of service object to get.</param>
        /// <returns>
        /// A service object of type <paramref name="serviceType"/>.-or- null if there is no service object of type <paramref name="serviceType"/>.
        /// </returns>
        public object GetService(Type serviceType)
        {
            if (serviceType == typeof(IDataServiceStreamProvider))
            {
                return this;
            }

            return null;
        }
    }
}
